#include "types.h"
#include "stat.h"
#include "user.h"
#include "uthread.h"
#include "signal.h"


// task 2.1

struct uthread ttable[MAX_THREAD];


//struct uthread *c_thread;
int c_thread = 0;

// task 2.2

void uthread_init()
{

	int i;
	ttable[0].tid = 0;
	STORE_ESP(ttable[0].esp);
	STORE_EBP(ttable[0].ebp);
	ttable[0].state = T_RUNNING;
	ttable[0].wait_num = 0;
	//initialize all other slots to FREE and assign TIDs for future use
	for (i = 1; i < MAX_THREAD; i ++)
	{
		ttable[i].tid = i;
		ttable[i].state = T_FREE;
	}
	signal(SIGALRM, uthread_yield);
	alarm(THREAD_QUANTA);
}

// task 2.3

int uthread_create(void (*start_func)(void *), void*arg)
{
	int i;
	int ticks;
	asm("pusha");
	ticks = getTicks();
	alarm(0);
	asm("popa");
	for (i = 0; i < MAX_THREAD; i++)
	{
		if (ttable[i].state == T_FREE)
		{	
			//allocate a stack for the new thread
			if ((ttable[i].stack = malloc(STACK_SIZE)) < 0)
			{
				alarm(ticks);
				return -1;
			}
			LOAD_ESP(ttable[i].stack + STACK_SIZE);
			PUSH(arg);
			PUSH(uthread_exit);
			PUSH(start_func);
			//added for the first "pop ebp"
			PUSH(0);
			STORE_ESP(ttable[i].ebp);
			ttable[i].esp = ttable[i].ebp;
			//set esp to current thread's esp
			LOAD_ESP(ttable[c_thread].esp);
			ttable[i].state = T_RUNNABLE;
			ttable[i].wait_num = 0;
			asm("pusha");
			alarm(ticks);
			asm("popa");
			return ttable[i].tid;
			
		}
	}
	alarm(ticks);
	//failed - no free thread to use
	return -1;
}

// task 2.4


void safe_exit()
{
	int tid;
	while (1)
	{
		for (tid = 1; tid< MAX_THREAD; tid++)
		{
			if (ttable[tid].state != T_FREE){
				uthread_join(tid);
				break;
			}
		}
		if (tid == MAX_THREAD){
			exit();
		}
	}
}

void uthread_switch()
{
	int i;
	int n_thread = (c_thread + 1) % MAX_THREAD;

	//finding a runnable thread
	for (i = 0; ttable[n_thread].state != T_RUNNABLE && i < MAX_THREAD; i++)
	{
		n_thread = (n_thread + 1) % MAX_THREAD;
	}
	STORE_ESP(ttable[c_thread].esp);
	STORE_EBP(ttable[c_thread].ebp);
	c_thread = n_thread;
	LOAD_EBP(ttable[c_thread].ebp);
	LOAD_ESP(ttable[c_thread].esp);
	alarm(THREAD_QUANTA);
}

void uthread_yield()
{
	//store registers on stack
	asm("pusha");
	alarm(0);
	ttable[c_thread].state = T_RUNNABLE;
	//change current thread
	uthread_switch();
	asm("popa");
}

// task 2.5

void uthread_awake()
{
	int i;
	for (i = 0; i < ttable[c_thread].wait_num; i++)
		ttable[ttable[c_thread].waiting[i]].state = T_RUNNABLE;
}

void uthread_exit()
{
	asm("pusha");
	alarm(0);
	asm("popa");
	ttable[c_thread].state = T_FREE;
	if (ttable[c_thread].tid != 0)
		free(ttable[c_thread].stack);
	else
		safe_exit();
	uthread_awake();
	uthread_switch();
	asm("popa");
}

// task 2.6

int uthread_self()
{
	return c_thread;
}

// task 2.7

void uthread_sleep()
{
	asm("pusha");
	alarm(0);
	ttable[c_thread].state = T_SLEEPING;
	uthread_switch();
	asm("popa");
}

int uthread_join(int tid)
{

	int ticks;
	asm("pusha");
	ticks = getTicks();
	alarm(0);
	asm("popa");
	if (ttable[tid].state != T_FREE)
	{
		//adding current thread to tid waiting list and going to sleep.
		ttable[tid].waiting[ttable[tid].wait_num++] = ttable[c_thread].tid;
		uthread_sleep();
		return 0;
	}
	asm("pusha");
	alarm(ticks);
	asm("popa");
	return -1;
}

void queue_init(struct tqueue *q)
{
	q->head = 0;
	q->tail = 0;
}

struct uthread* dequeue(struct binary_semaphore* semaphore)
{
	if (semaphore->queue->head == semaphore->queue->tail)
		return 0;
	return semaphore->queue->uthreads[semaphore->queue->tail++ % MAX_THREAD];
}

void enqueue(struct binary_semaphore* semaphore)
{
	semaphore->queue->uthreads[semaphore->queue->head++ % MAX_THREAD] = &ttable[c_thread];
}

void binary_semaphore_init(struct binary_semaphore* semaphore, int value)
{
	semaphore->queue = malloc(sizeof(struct tqueue));
	if (!value)
		semaphore->state = S_LOCKED;
	else
		semaphore->state = S_FREE;
}
void binary_semaphore_down(struct binary_semaphore* semaphore)
{
	//store amount of ticks left and prevent scheduler from switching
	int ticks;
	asm("pusha");
	ticks = getTicks();
	alarm(0);
	asm("popa");
	if (semaphore->state == S_LOCKED)
	{
		enqueue(semaphore);
		uthread_sleep();
		alarm(0);
	}
	semaphore->state = S_LOCKED;
	//allow the thread to run its remaining time
	alarm(ticks);

}
void binary_semaphore_up(struct binary_semaphore* semaphore)
{
	int ticks;
	struct uthread *next;
	asm("pusha");
	ticks = getTicks();
	alarm(0);
	asm("popa");


	if ((next = dequeue(semaphore)) == 0)
	{
		//no thread is waiting. release semaphore and restore alarm
		semaphore->state = S_FREE;
		asm("pusha");
		alarm(ticks);
		asm("popa");
	}
	else
	{
		//threads are waiting. wake up the next thread and yield.
		next->state = T_RUNNABLE;
		uthread_yield();
	}
}

